home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / cmds / tcsh / dist / tw.parse.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-12-21  |  38.9 KB  |  1,556 lines

  1. /* $Header: /home/hyperion/mu/christos/src/sys/tcsh-6.01/RCS/tw.parse.c,v 3.18 1991/12/19 21:40:06 christos Exp $ */
  2. /*
  3.  * tw.parse.c: Everyone has taken a shot in this futile effort to
  4.  *           lexically analyze a csh line... Well we cannot good
  5.  *           a job as good as sh.lex.c; but we try. Amazing that
  6.  *           it works considering how many hands have touched this code
  7.  */
  8. /*-
  9.  * Copyright (c) 1980, 1991 The Regents of the University of California.
  10.  * All rights reserved.
  11.  *
  12.  * Redistribution and use in source and binary forms, with or without
  13.  * modification, are permitted provided that the following conditions
  14.  * are met:
  15.  * 1. Redistributions of source code must retain the above copyright
  16.  *    notice, this list of conditions and the following disclaimer.
  17.  * 2. Redistributions in binary form must reproduce the above copyright
  18.  *    notice, this list of conditions and the following disclaimer in the
  19.  *    documentation and/or other materials provided with the distribution.
  20.  * 3. All advertising materials mentioning features or use of this software
  21.  *    must display the following acknowledgement:
  22.  *    This product includes software developed by the University of
  23.  *    California, Berkeley and its contributors.
  24.  * 4. Neither the name of the University nor the names of its contributors
  25.  *    may be used to endorse or promote products derived from this software
  26.  *    without specific prior written permission.
  27.  *
  28.  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  29.  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  30.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  31.  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
  32.  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  33.  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  34.  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  35.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  36.  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  37.  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  38.  * SUCH DAMAGE.
  39.  */
  40. #include "sh.h"
  41.  
  42. RCSID("$Id: tw.parse.c,v 3.18 1991/12/19 21:40:06 christos Exp $")
  43.  
  44. #include "tw.h"
  45. #include "ed.h"
  46. #include "tc.h"
  47.  
  48. /* #define TENEDEBUG */
  49.  
  50. /* true if the path has relative elements */
  51. static bool relatives_in_path;
  52.  
  53. static int maxitems = 0;
  54. Char  **command_list = (Char **) NULL;    /* the pre-digested list of commands
  55.                      * for speed and general usefullness */
  56. int     numcommands = 0;
  57. int     have_sorted = 0;
  58.  
  59. /* Set to TRUE if recexact is set and an exact match is found
  60.  * along with other, longer, matches.
  61.  */
  62. int non_unique_match = FALSE;
  63.  
  64. #ifdef notdef
  65. int     dirctr = 0;        /* -1 0 1 2 ... */
  66.  
  67. #endif
  68. Char    dirflag[5];        /* ' nn\0' - dir #s -  . 1 2 ... */
  69.  
  70. static bool SearchNoDirErr = 0;    /* t_search returns -2 if dir is unreadable */
  71.  
  72. /* do the expand or list on the command line -- SHOULD BE REPLACED */
  73.  
  74. extern Char NeedsRedraw;    /* from ed.h */
  75. extern int TermH;        /* from the editor routines */
  76. extern int lbuffed;        /* from sh.print.c */
  77.  
  78. static    void     free_items        __P((Char **, int));
  79. static    void     extract_dir_and_name    __P((Char *, Char *, Char *));
  80. static    Char    *quote_meta        __P((Char *, bool));
  81. static    Char    *getentry        __P((DIR *, int));
  82. static    Char    *dollar            __P((Char *, Char *));
  83. static    Char    *tilde            __P((Char *, Char *));
  84. static    Char     filetype        __P((Char *, Char *));
  85. static    int     t_glob            __P((Char ***));
  86. static    int     is_prefix        __P((Char *, Char *));
  87. static    int     is_suffix        __P((Char *, Char *));
  88. static    int     recognize        __P((Char *, Char *, int, int));
  89. static    int     ignored        __P((Char *));
  90. static    void     tw_get_comm_list    __P((void));
  91. static    int     isadirectory        __P((Char *, Char *));
  92.  
  93. /*
  94.  * If we find a set command, then we break a=b to a= and word becomes
  95.  * b else, we don't break a=b.
  96.  */
  97. #define isaset(c, w) ((w)[-1] == '=' && \
  98.               ((c)[0] == 's' && (c)[1] == 'e' && (c)[2] == 't' && \
  99.                ((c[3] == ' ' || (c)[3] == '\t'))))
  100. /*
  101.  * Return value for tenematch():
  102.  *  > 1:    No. of items found
  103.  *  = 1:    Exactly one match / spelling corrected
  104.  *  = 0:    No match / spelling was correct
  105.  *  < 0:    Error (incl spelling correction impossible)
  106.  */
  107. int
  108. tenematch(inputline, inputline_size, num_read, command)
  109.     Char   *inputline;        /* match string prefix */
  110.     int     inputline_size;    /* max size of string */
  111.     int     num_read;        /* # actually in inputline */
  112.     COMMAND command;        /* LIST or RECOGNIZE or PRINT_HELP */
  113.  
  114. {
  115.     Char    word[FILSIZ + 1];
  116.     register Char *str_end, *word_start, *cmd_start, *wp;
  117.     Char   *cmd_st;
  118.     int     space_left;
  119.     int     is_a_cmd;        /* UNIX command rather than filename */
  120.     int     search_ret;        /* what search returned for debugging */
  121.     /* 
  122.      * XXX: Avoid gcc bug. If in_single and in_double are ints 
  123.      * then they always stay 0.
  124.      */
  125.     Char    in_single, in_double;    /* In single or in_double quotes */
  126.     int     backq, skp;
  127.  
  128.     str_end = &inputline[num_read];
  129.  
  130.     /*
  131.      * Check if in backquotes
  132.      */
  133.     for (cmd_st = str_end, backq = 0;
  134.      cmd_st >= inputline;
  135.      backq ^= (*cmd_st-- == '`'));
  136.     /*
  137.      * space backward looking for the beginning of this command
  138.      */
  139.     for (cmd_st = str_end; cmd_st > inputline; --cmd_st)
  140.     if ((iscmdmeta(cmd_st[-1]) || (cmd_st[-1] == '`' && backq))
  141.         && ((cmd_st - 1 == inputline) || (cmd_st[-2] != '\\')))
  142.         break;
  143.     /* step forward over leading spaces */
  144.     while (*cmd_st != '\0' && (*cmd_st == ' ' || *cmd_st == '\t'))
  145.     cmd_st++;
  146.  
  147.     /*
  148.      * Find LAST occurence of a delimiter in the inputline. The word start is
  149.      * one character past it.
  150.      */
  151.     for (word_start = str_end, skp = 0; word_start > inputline; --word_start) {
  152.     if (!backq && word_start[-1] == '`') {
  153.         skp ^= 1;
  154.         continue;
  155.     }
  156.     if (skp)
  157.         continue;
  158.     if ((ismetahash(word_start[-1]) || isaset(cmd_st, word_start)) &&
  159.         (word_start[-1] != '$') &&
  160.         ((word_start - 1 == inputline) || (word_start[-2] != '\\')))
  161.         break;
  162.     }
  163.  
  164.  
  165.  
  166. #ifdef masscomp
  167.     /*
  168.      * Avoid a nasty message from the RTU 4.1A & RTU 5.0 compiler concerning
  169.      * the "overuse of registers". According to the compiler release notes,
  170.      * incorrect code may be produced unless the offending expression is
  171.      * rewritten. Therefore, we can't just ignore it, DAS DEC-90.
  172.      */
  173.     space_left = inputline_size;
  174.     space_left -= word_start - inputline + 1;
  175. #else
  176.     space_left = inputline_size - (word_start - inputline) - 1;
  177. #endif
  178.  
  179.     /*
  180.      * Quote args
  181.      */
  182.     in_double = 0;
  183.     in_single = 0;
  184.     for (cmd_start = word_start, wp = word; 
  185.      cmd_start < str_end && wp <= word + FILSIZ; cmd_start++) 
  186.     switch (*cmd_start) {
  187.     case '\'':
  188.         if (!in_double) {
  189.         if (in_single)
  190.             in_single = 0;
  191.         else
  192.             in_single = QUOTE;
  193.         }
  194.         else
  195.         *wp++ = *cmd_start | QUOTE;
  196.         break;
  197.     case '"':
  198.         if (!in_single) {
  199.         if (in_double)
  200.             in_double = 0;
  201.         else
  202.             in_double = QUOTE;
  203.         }
  204.         else
  205.         *wp++ = *cmd_start | QUOTE;
  206.         break;
  207.     case '/':
  208.         /*
  209.          * This is so that the recognize stuff works easily
  210.          */
  211.         *wp++ = *cmd_start;
  212.         break;
  213.     case '\\':
  214.         if (in_single || in_double)
  215.         *wp++ = *cmd_start | QUOTE;
  216.         else
  217.         *wp++ = *++cmd_start | QUOTE;
  218.         break;
  219.     default:
  220.         *wp++ = *cmd_start | in_single;
  221.         break;
  222.     }
  223.     if (wp > word + FILSIZ)
  224.     return (-1);
  225.     *wp = '\0';
  226.     /*
  227.      * Move the word_start further if still in quotes, cause the
  228.      * quotes so far have no effect.
  229.      */
  230.     if ((in_single || in_double) && (*word_start == '\'' || *word_start == '"'))
  231.     word_start++;
  232.  
  233.     is_a_cmd = starting_a_command(word_start - 1, inputline);
  234. #ifdef TENEDEBUG
  235.     xprintf("starting_a_command %d\n", is_a_cmd);
  236.     xprintf("\ncmd_st:%s:\n", short2str(cmd_st));
  237.     xprintf("word:%s:\n", short2str(word));
  238.     xprintf("word:");
  239.     for (wp = word; *wp; wp++)
  240.     xprintf("%c", *wp & QUOTE ? '-' : ' ');
  241.     xprintf(":\n");
  242. #endif
  243.     switch ((int) command) {
  244.     Char    buffer[FILSIZ + 1], *bptr;
  245.     Char   *slshp;
  246.     Char   *items[2], **ptr;
  247.     int     i, count;
  248.  
  249.     case RECOGNIZE:
  250.     if (adrof(STRautocorrect)) {
  251.         if ((slshp = Strrchr(word, '/')) != NULL && slshp[1] != '\0') {
  252.         SearchNoDirErr = 1;
  253.         for (bptr = word; bptr < slshp; bptr++) {
  254.             /*
  255.              * do not try to correct spelling of words containing
  256.              * globbing characters
  257.              */
  258.             if (isglob(*bptr)) {
  259.             SearchNoDirErr = 0;
  260.             break;
  261.             }
  262.         }
  263.         }
  264.     }
  265.     else
  266.         slshp = STRNULL;
  267.     search_ret = t_search(word, wp, command, space_left, is_a_cmd, 1);
  268.     SearchNoDirErr = 0;
  269.  
  270.     if (search_ret == -2) {
  271.         Char    rword[FILSIZ + 1];
  272.  
  273.         (void) Strcpy(rword, slshp);
  274.         if (slshp != STRNULL)
  275.         *slshp = '\0';
  276.         if ((search_ret = spell_me(word, sizeof(word), is_a_cmd)) == 1) {
  277.         DeleteBack(str_end - word_start);/* get rid of old word */
  278.         (void) Strcat(word, rword);
  279.         if (InsertStr(word) < 0)    /* insert newly spelled word */
  280.             return -1;    /* error inserting */
  281.         wp = word + Strlen(word);
  282.         search_ret = t_search(word, wp, command, space_left,
  283.                       is_a_cmd, 1);
  284.         }
  285.     }
  286.  
  287.     /*
  288.      * Change by Christos Zoulas: if the name has metachars in it, quote
  289.      * the metachars, but only if we are outside quotes.
  290.      * We don't quote the last space if we had a unique match and 
  291.      * addsuffix was set. Otherwise the last space was part of a word.
  292.      */
  293.     if (*wp && InsertStr((in_single || in_double) ?
  294.                  wp : quote_meta(wp, search_ret == 1 &&
  295.                          (bool) is_set(STRaddsuffix))) < 0)
  296.         /* put it in the input buffer */
  297.         return -1;        /* error inserting */
  298.     return search_ret;
  299.  
  300.     case SPELL:
  301.     for (bptr = word_start; bptr < str_end; bptr++) {
  302.         /*
  303.          * do not try to correct spelling of words containing globbing
  304.          * characters
  305.          */
  306.         if (isglob(*bptr))
  307.         return 0;
  308.     }
  309.     if ((search_ret = spell_me(word, sizeof(word), is_a_cmd)) == 1) {
  310.         DeleteBack(str_end - word_start);    /* get rid of old word */
  311.         if (InsertStr(word) < 0)    /* insert newly spelled word */
  312.         return -1;    /* error inserting */
  313.     }
  314.     return search_ret;
  315.  
  316.     case PRINT_HELP:
  317.     do_help(cmd_st);
  318.     return 1;
  319.  
  320.     case GLOB:
  321.     case GLOB_EXPAND:
  322.     (void) Strncpy(buffer, word, FILSIZ + 1);
  323.     items[0] = buffer;
  324.     items[1] = NULL;
  325.     ptr = items;
  326.     if (is_a_cmd) {
  327.         xprintf("\nSorry no globbing for commands yet..\n");
  328.         return -1;
  329.     }
  330.     if ((count = t_glob(&ptr)) > 0) {
  331.         if (command == GLOB)
  332.         print_by_column(STRNULL, ptr, count, is_a_cmd);
  333.         else {
  334.         DeleteBack(str_end - word_start);/* get rid of old word */
  335.         for (i = 0; i < count; i++)
  336.             if (ptr[i] && *ptr[i]) {
  337.             if (InsertStr((in_single || in_double) ?
  338.                       ptr[i] : quote_meta(ptr[i], 0)) < 0 ||
  339.                 InsertStr(STRspace) < 0) {
  340.                 blkfree(ptr);
  341.                 return (-1);
  342.             }
  343.             }
  344.         }
  345.         blkfree(ptr);
  346.     }
  347.     return count;
  348.  
  349.     case VARS_EXPAND:
  350.     if (dollar(buffer, word)) {
  351.         DeleteBack(str_end - word_start);
  352.         if (InsertStr((in_single || in_double) ?
  353.               buffer : quote_meta(buffer, 0)) < 0)
  354.         return (-1);
  355.         return (1);
  356.     }
  357.     return (0);
  358.  
  359.     case PATH_NORMALIZE:
  360.     if ((bptr = dnormalize(word)) != NULL) {
  361.         (void) Strcpy(buffer, bptr);
  362.         xfree((ptr_t) bptr);
  363.         DeleteBack(str_end - word_start);
  364.         if (InsertStr((in_single || in_double) ?
  365.               buffer : quote_meta(buffer, 0)) < 0)
  366.         return (-1);
  367.         return (1);
  368.     }
  369.     return (0);
  370.  
  371.     case LIST:
  372.     search_ret = t_search(word, wp, command, space_left, is_a_cmd, 1);
  373.     return search_ret;
  374.  
  375.     default:
  376.     xprintf("tcsh: Internal match error.\n");
  377.     return 1;
  378.  
  379.     }
  380. }
  381.  
  382.  
  383.  
  384.  
  385. static int
  386. t_glob(v)
  387.     register Char ***v;
  388. {
  389.     jmp_buf osetexit;
  390.  
  391.     if (**v == 0)
  392.     return (0);
  393.     gflag = 0, tglob(*v);
  394.     if (gflag) {
  395.     getexit(osetexit);    /* make sure to come back here */
  396.     if (setexit() == 0)
  397.         *v = globall(*v);
  398.     resexit(osetexit);
  399.     gargv = 0;
  400.     if (haderr) {
  401.         haderr = 0;
  402.         NeedsRedraw = 1;
  403.         return (-1);
  404.     }
  405.     if (*v == 0)
  406.         return (0);
  407.     }
  408.     else
  409.     return (0);
  410.  
  411.     return (gargc);
  412. }
  413.  
  414.  
  415. /*
  416.  * quote (\) the meta-characters in a word
  417.  * except trailing space if trail_space is set
  418.  * return pointer to quoted word in static storage
  419.  */
  420. static Char *
  421. quote_meta(word, trail_space)
  422.     Char   *word;
  423.     bool    trail_space;
  424. {
  425.     static Char buffer[2 * FILSIZ + 1], *bptr, *wptr;
  426.  
  427.     for (bptr = buffer, wptr = word; *wptr != '\0';) {
  428.     if ((cmap(*wptr, _META | _DOL | _Q | _ESC | _GLOB) || *wptr == HIST ||
  429.          *wptr == HISTSUB) &&
  430.         (*wptr != ' ' || !trail_space || 
  431.          *(wptr + 1) != '\0') && *wptr != '#')
  432.         *bptr++ = '\\';
  433.     *bptr++ = *wptr++;
  434.     }
  435.     *bptr = '\0';
  436.     return (buffer);
  437. }
  438.  
  439.  
  440. /*
  441.  * return true if check items initial chars in template
  442.  * This differs from PWB imatch in that if check is null
  443.  * it items anything
  444.  */
  445.  
  446. static int
  447. is_prefix(check, template)
  448.     register Char *check, *template;
  449. {
  450.     for (; *check; check++, template++)
  451.     if ((*check & TRIM) != (*template & TRIM))
  452.         return (FALSE);
  453.     return (TRUE);
  454. }
  455.  
  456. /*
  457.  *  Return true if the chars in template appear at the
  458.  *  end of check, I.e., are it's suffix.
  459.  */
  460. static int
  461. is_suffix(check, template)
  462.     register Char *check, *template;
  463. {
  464.     register Char *t, *c;
  465.  
  466.     for (t = template; *t++;);
  467.     for (c = check; *c++;);
  468.     for (;;) {
  469.     if (t == template)
  470.         return 1;
  471.     --t;
  472.     --c;
  473.     if (c == check || (*t & TRIM) != (*c & TRIM))
  474.         return 0;
  475.     }
  476. }
  477.  
  478. static int
  479. ignored(entry)
  480.     register Char *entry;
  481. {
  482.     struct varent *vp;
  483.     register Char **cp;
  484.  
  485.     if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL)
  486.     return (FALSE);
  487.     for (; *cp != NULL; cp++)
  488.     if (is_suffix(entry, *cp))
  489.         return (TRUE);
  490.     return (FALSE);
  491. }
  492.  
  493. /* return true if the command starting at wordstart is a command */
  494.  
  495. #define EVEN(x) (((x) & 1) != 1)
  496.  
  497. int
  498. starting_a_command(wordstart, inputline)
  499.     register Char *wordstart, *inputline;
  500. {
  501.     register Char *ptr, *ncmdstart;
  502.     int     count;
  503.     static  Char
  504.             cmdstart[] = {'`', ';', '&', '(', '|', '\0'},
  505.             cmdalive[] = {' ', '\t', '\'', '"', '<', '>', '\0'};
  506.  
  507.     /*
  508.      * Find if the number of backquotes is odd or even.
  509.      */
  510.     for (ptr = wordstart, count = 0;
  511.      ptr >= inputline;
  512.      count += (*ptr-- == '`'));
  513.     /*
  514.      * if the number of backquotes is even don't include the backquote char in
  515.      * the list of command starting delimiters [if it is zero, then it does not
  516.      * matter]
  517.      */
  518.     ncmdstart = cmdstart + EVEN(count);
  519.  
  520.     /*
  521.      * look for the characters previous to this word if we find a command
  522.      * starting delimiter we break. if we find whitespace and another previous
  523.      * word then we are not a command
  524.      * 
  525.      * count is our state machine: 0 looking for anything 1 found white-space
  526.      * looking for non-ws
  527.      */
  528.     for (count = 0; wordstart >= inputline; wordstart--) {
  529.     if (*wordstart == '\0')
  530.         continue;
  531.     if (Strchr(ncmdstart, *wordstart))
  532.         break;
  533.     /*
  534.      * found white space
  535.      */
  536.     if (ptr = Strchr(cmdalive, *wordstart))
  537.         count = 1;
  538.     if (count == 1 && !ptr)
  539.         return (FALSE);
  540.     }
  541.  
  542.     if (wordstart > inputline)
  543.     switch (*wordstart) {
  544.     case '&':        /* Look for >& */
  545.         while (wordstart > inputline &&
  546.            (*--wordstart == ' ' || *wordstart == '\t'));
  547.         if (*wordstart == '>')
  548.         return (FALSE);
  549.         break;
  550.     case '(':        /* check for foreach, if etc. */
  551.         while (wordstart > inputline &&
  552.            (*--wordstart == ' ' || *wordstart == '\t'));
  553.         if (!iscmdmeta(*wordstart) &&
  554.         (*wordstart != ' ' && *wordstart != '\t'))
  555.         return (FALSE);
  556.         break;
  557.     default:
  558.         break;
  559.     }
  560.     return (TRUE);
  561. }
  562.  
  563.  
  564.  
  565. /*
  566.  * Object: extend what user typed up to an ambiguity.
  567.  * Algorithm:
  568.  * On first match, copy full entry (assume it'll be the only match)
  569.  * On subsequent matches, shorten extended_name to the first
  570.  * character mismatch between extended_name and entry.
  571.  * If we shorten it back to the prefix length, stop searching.
  572.  */
  573. static int
  574. recognize(extended_name, entry, name_length, numitems)
  575.     Char   *extended_name, *entry;
  576.     int     name_length, numitems;
  577. {
  578.     if (numitems == 1)        /* 1st match */
  579.     copyn(extended_name, entry, MAXNAMLEN);
  580.     else {            /* 2nd and subsequent matches */
  581.     register Char *x, *ent;
  582.     register int len = 0;
  583.  
  584.     for (x = extended_name, ent = entry;
  585.          *x && (*x & TRIM) == (*ent & TRIM); x++, len++, ent++);
  586.     *x = '\0';        /* Shorten at 1st char diff */
  587.     if (len == name_length)    /* Ambiguous to prefix? */
  588.         return (-1);    /* So stop now and save time */
  589.     }
  590.     return (0);
  591. }
  592.  
  593.  
  594.  
  595. /*
  596.  * Perform a RECOGNIZE or LIST command on string "word".
  597.  *
  598.  * Return value:
  599.  *  >= 0:   SPELL command: "distance" (see spdist())
  600.  *          other:         No. of items found
  601.  *  < 0:    Error (message or beep is output)
  602.  */
  603.  
  604. /*ARGSUSED*/
  605. int
  606. t_search(word, wp, command, max_word_length, looking_for_command, list_max)
  607.     Char   *word, *wp;        /* original end-of-word */
  608.     COMMAND command;
  609.     int     max_word_length, looking_for_command, list_max;
  610. {
  611.     register ignoring = 1, nignored = 0;
  612.     register name_length,    /* Length of prefix (file name) */
  613.             looking_for_lognames;    /* True if looking for login names */
  614.     int     showpathn;        /* True if we want path number */
  615.     Char    tilded_dir[FILSIZ + 1],    /* dir after ~ expansion */
  616.             dollar_dir[FILSIZ + 1],    /* dir after $ expansion */
  617.             dir[FILSIZ + 1],    /* /x/y/z/ part in /x/y/z/f */
  618.             name[MAXNAMLEN + 1],/* f part in /d/d/d/f */
  619.             extended_name[MAXNAMLEN + 1],    /* the recognized (extended)
  620.                          * name */
  621.            *entry = NULL,    /* single directory entry or logname */
  622.            *target;        /* Target to expand/correct/list */
  623.     int     next_command = 0;    /* the next command to take out of */
  624.  
  625.     /* the list of commands */
  626.     int     looking_for_shellvar,    /* true if looking for $foo */
  627.             looking_for_file;    /* true if looking for a file name */
  628.     Char  **pathv;        /* pointer to PATH elements */
  629.     struct varent *vptr = NULL;/* current shell variable position */
  630.     Char  **envptr = NULL;    /* current env. variable position */
  631.  
  632.     int     d = 4, nd;        /* distance and new distance to command for
  633.                  * SPELL */
  634.     int     exec_check = 0, dir_ok = 0;    /* need to check
  635.                      * executability/directory */
  636.  
  637.     static  Char        /* For unset path         */
  638.     *       pv[2] = {STRNULL, NULL};
  639.     static  DIR
  640.     *       dir_fd = NULL;
  641.     static  Char
  642.     **      items = NULL;    /* file names when doing a LIST */
  643.  
  644.     /*
  645.      * bugfix by Marty Grossman (grossman@CC5.BBN.COM): directory listing can
  646.      * dump core when interrupted
  647.      */
  648.     static int numitems;
  649.  
  650.     pathv = (vptr = adrof(STRPATH)) == NULL ? pv : vptr->vec;
  651.  
  652.     if (items != NULL)
  653.     FREE_ITEMS(items, numitems);
  654.     numitems = 0;
  655.     if (dir_fd != NULL)
  656.     FREE_DIR(dir_fd);
  657.  
  658.     non_unique_match = FALSE;    /* See the recexact code below */
  659.  
  660.     extract_dir_and_name(word, dir, name);
  661.     looking_for_lognames = (*word == '~') && (Strchr(word, '/') == NULL);
  662.     looking_for_shellvar = (target = Strrchr(name, '$')) &&
  663.     (Strchr(name, '/') == NULL);
  664.     looking_for_file = (!looking_for_command && !looking_for_lognames &&
  665.             !looking_for_shellvar) || Strchr(word, '/');
  666.  
  667.     /* PWP: don't even bother when doing ALL of the commands */
  668.     if (looking_for_command && (*word == '\0')) 
  669.     return (-1);
  670.     tilded_dir[0] = '\0';
  671.     dollar_dir[0] = '\0';
  672.  
  673.     if (looking_for_shellvar) {    /* Looking for a shell var? */
  674.     vptr = tw_start_shell_list();
  675.     envptr = tw_start_env_list();
  676.     target++;
  677.     }
  678.     else
  679.     target = name;
  680.     if (looking_for_shellvar || looking_for_file) {
  681.     Char   *nd = NULL;
  682.  
  683.     /* Open the directory */
  684.     /* expand ~user/... and variables stuff */
  685.     if ((dollar(dollar_dir, dir) == 0) ||
  686.         (tilde(tilded_dir, dollar_dir) == 0) ||
  687.         !(nd = dnormalize(*tilded_dir ? tilded_dir : STRdot)) ||
  688.         ((dir_fd = opendir(short2str(nd))) == NULL)) {
  689.         xfree((ptr_t) nd);
  690.         if (command == SPELL || SearchNoDirErr)
  691.         return (-2);
  692.         /*
  693.          * From: Amos Shapira <amoss@cs.huji.ac.il>
  694.          * Print a better message when completion fails
  695.          */
  696.         xprintf("\n%s %s\n",
  697.             *tilded_dir ? short2str(tilded_dir) :
  698.             (*dollar_dir ? short2str(dollar_dir) : short2str(dir)),
  699.             (errno == ENOTDIR ? "not a directory" :
  700.             (errno == ENOENT ? "not found" : "unreadable")));
  701.         NeedsRedraw = 1;
  702.         return (-1);
  703.     }
  704.     if (nd) {
  705.         if (*tilded_dir != '\0') {
  706.         Char   *s, *d, *p;
  707.  
  708.         /*
  709.          * Copy and append a / if there was one
  710.          */
  711.         for (p = tilded_dir; *p; p++);
  712.         if (*--p == '/') {
  713.             for (p = nd; *p; p++);
  714.             if (*--p != '/')
  715.             p = NULL;
  716.         }
  717.         for (d = tilded_dir, s = nd; *d++ = *s++;);
  718.         if (!p) {
  719.             *d-- = '\0';
  720.             *d = '/';
  721.         }
  722.         }
  723.         xfree((ptr_t) nd);
  724.     }
  725.     }
  726.     else if (looking_for_lognames) {    /* Looking for login names? */
  727.     /*
  728.      * Check if the spelling was already correct
  729.      * From: Rob McMahon <cudcv@cu.warwick.ac.uk>
  730.      */
  731.     if (command == SPELL && getpwnam(short2str(&word[1])) != NULL) {
  732. #ifdef YPBUGS
  733.         fix_yp_bugs();
  734. #endif /* YPBUGS */
  735.         return (0);
  736.     }
  737.     copyn(name, &word[1], MAXNAMLEN);    /* name sans ~ */
  738.     (void) setpwent();    /* Open passwd file */
  739.     }
  740.     else if (looking_for_command) {
  741.     if (!numcommands)    /* if we have no list of commands */
  742.         tw_get_comm_list();
  743.     if (!have_sorted) {    /* if we haven't sorted them yet */
  744.         tw_add_builtins();
  745.         tw_add_aliases();
  746.         tw_sort_comms();    /* re-build the command path for twenex.c */
  747.     }
  748.     copyn(target, word, MAXNAMLEN);    /* so it can match things */
  749.     }
  750.     else {
  751.     xprintf("\ntcsh internal error: I don't know what I'm looking for!\n");
  752.     NeedsRedraw = 1;
  753.     return (-1);
  754.     }
  755.  
  756.  
  757. again:
  758.     name_length = Strlen(target);
  759.     showpathn = looking_for_command && is_set(STRlistpathnum);
  760.  
  761.     while (1) {
  762.     if (looking_for_shellvar) {
  763.         if ((entry = tw_next_shell_var(&vptr)) == NULL)
  764.         if ((entry = tw_next_env_var(&envptr)) == NULL)
  765.             break;
  766.     }
  767.     else if (looking_for_file || looking_for_lognames) {
  768.         if ((entry = getentry(dir_fd, looking_for_lognames)) == NULL) {
  769.         break;
  770.         }
  771.  
  772.         /*
  773.          * Don't match . files on null prefix match
  774.          */
  775.         if (name_length == 0 && entry[0] == '.' &&
  776.         !looking_for_lognames && !is_set(STRshowdots))
  777.         continue;
  778.         if (looking_for_command && !looking_for_lognames) {
  779.         exec_check = 1;
  780.         dir_ok = 1;
  781.         }
  782.     }
  783.     else if (looking_for_command) {
  784. #ifdef  NOTDEF            /* Not possible */
  785.         if (numcommands == 0) {
  786.         dohash(NULL, NULL);
  787.         }
  788. #endif
  789.         /* searching . added by Andreas Luik <luik@isaak.isa.de> */
  790.  
  791.         if ((next_command < numcommands) &&
  792.         (entry = command_list[next_command]) == NULL)
  793.         next_command = numcommands;
  794.         if (next_command >= numcommands) {    /* search relative elems */
  795.         if (!relatives_in_path)
  796.             break;    /* we don't need to do it */
  797.         while ((dir_fd == NULL ||
  798.             (entry = getentry(dir_fd, FALSE)) == NULL) &&
  799.                *pathv) {
  800.             if (dir_fd != NULL)
  801.             FREE_DIR(dir_fd);
  802.             entry = NULL;
  803.             while (*pathv && pathv[0][0] == '/')
  804.             pathv++;
  805.             if (*pathv) {
  806.             /*
  807.              * We complete directories only on '.' should that
  808.              * be changed?
  809.              */
  810.             if (pathv[0][0] == '\0' ||
  811.                 (pathv[0][0] == '.' && pathv[0][1] == '\0')) {
  812.                 *tilded_dir = '\0';
  813.                 dir_fd = opendir(".");
  814.                 dir_ok = 1;    
  815.             }
  816.             else {
  817.                 copyn(tilded_dir, *pathv, FILSIZ);
  818.                 catn(tilded_dir, STRslash, FILSIZ);
  819.                 dir_fd = opendir(short2str(*pathv));
  820.                 dir_ok = 0;
  821.             }
  822.             pathv++;
  823.             }
  824.         }
  825.         if (entry == NULL)
  826.             break;    /* end of PATH */
  827.         /*
  828.          * executability check for other than "." should perhaps be
  829.          * conditional on recognize_only_executables?
  830.          */
  831.         exec_check = 1;
  832.         }
  833.         else
  834.         next_command++;
  835.     }
  836.  
  837.     if (command == SPELL) {    /* correct the spelling of the last bit */
  838.         if (name_length == 0) {/* zero-length word can't be misspelled */
  839.         extended_name[0] = '\0';/* (not trying is important for ~) */
  840.         d = 0;
  841.         break;
  842.         }
  843.         nd = spdist(entry, target);    /* test the entry against original */
  844.         if (nd <= d && nd != 4) {
  845.         if (exec_check && !executable(tilded_dir, entry, dir_ok))
  846.             continue;
  847.         (void) Strcpy(extended_name, entry);
  848.         d = nd;
  849.         if (d == 0)    /* if found it exactly */
  850.             break;
  851.         }
  852.         else if (nd == 4) {
  853.         if (spdir(extended_name, tilded_dir, entry, target)) {
  854.             if (exec_check &&
  855.             !executable(tilded_dir, extended_name, dir_ok))
  856.             continue;
  857.             d = 0;
  858.             break;
  859.         }
  860.         }
  861.     }
  862.     else if (command == LIST) {    /* LIST command */
  863.         register int length;
  864.         register long i;
  865.         register Char **ni, **p2;
  866.  
  867.         if (!is_prefix(target, entry))
  868.         continue;
  869.         if (exec_check && !executable(tilded_dir, entry, dir_ok))
  870.         continue;
  871.  
  872.         if (items == NULL || maxitems == 0) {
  873.         items = (Char **) xmalloc((size_t) (sizeof(items[0]) *
  874.                             (ITEMS_START + 1)));
  875.         maxitems = ITEMS_START;
  876.         for (i = 0, p2 = items; i < maxitems; i++)
  877.             *p2++ = NULL;
  878.         }
  879.         else if (numitems >= maxitems) {
  880.         ni = (Char **) xrealloc((ptr_t) items, (size_t)
  881.                 (sizeof(items[0])) * (maxitems + ITEMS_INCR));
  882.         items = ni;
  883.         maxitems += ITEMS_INCR;
  884.         }
  885.  
  886.  
  887.         length = Strlen(entry) + 1;
  888.         if (showpathn)
  889.         length += Strlen(dirflag);
  890.         if (!looking_for_lognames && !looking_for_shellvar)
  891.         length++;
  892.  
  893.         /* safety check */
  894.         items[numitems] = (Char *) xmalloc((size_t)(length * sizeof(Char)));
  895.  
  896.         copyn(items[numitems], entry, MAXNAMLEN);
  897.  
  898.         if (!looking_for_lognames && !looking_for_shellvar
  899.         && !(looking_for_command && !looking_for_file)) {
  900.         Char    typestr[2];
  901.  
  902.         typestr[0] = filetype(tilded_dir, entry);
  903.         typestr[1] = '\0';
  904.         catn(items[numitems], typestr, MAXNAMLEN);
  905.         }
  906.  
  907.         if (showpathn)
  908.         catn(items[numitems], dirflag, MAXNAMLEN);
  909.         numitems++;
  910.     }
  911.     else {            /* RECOGNIZE command */
  912.         if (!is_prefix(target, entry))
  913.         continue;
  914.         if (exec_check && !executable(tilded_dir, entry, dir_ok))
  915.         continue;
  916.  
  917.         if (ignoring && ignored(entry)) {
  918.         nignored++;
  919.         continue;
  920.         }
  921.         if (is_set(STRrecexact)) {
  922.         if (StrQcmp(target, entry) == 0) {    /* EXACT match */
  923.             copyn(extended_name, entry, MAXNAMLEN);
  924.             numitems = 1;    /* fake into expanding */
  925.             non_unique_match = TRUE;
  926.             break;
  927.         }
  928.         }
  929.         if (recognize(extended_name, entry, name_length, ++numitems))
  930.         break;
  931.     }
  932.     }
  933.  
  934.     if (ignoring && numitems == 0 && nignored > 0) {
  935.     ignoring = 0;
  936.     nignored = 0;
  937.     if (looking_for_lognames)
  938.         (void) setpwent();
  939.     else
  940.         rewinddir(dir_fd);
  941.     goto again;
  942.     }
  943.     if (looking_for_lognames) {
  944. #ifdef YPBUGS
  945.     fix_yp_bugs();
  946. #endif                /* YPBUGS */
  947.     (void) endpwent();
  948.     }
  949.     else if (looking_for_file || looking_for_shellvar ||
  950.          (looking_for_command && relatives_in_path)) {
  951.     if (dir_fd != NULL)
  952.         FREE_DIR(dir_fd);
  953.     }
  954.  
  955.     if (command == RECOGNIZE) {
  956.     if (numitems > 0) {
  957.         if (looking_for_lognames)
  958.         copyn(word, STRtilde, 1);
  959.         else if (looking_for_shellvar) {
  960.         Char   *ptr = Strrchr(word, '$');
  961.  
  962.         *++ptr = '\0';    /* Delete after the dollar */
  963.         }
  964.         else if (looking_for_file)
  965.         copyn(word, dir, max_word_length);    /* put back dir part */
  966.         else
  967.         word[0] = '\0';
  968.         catn(word, extended_name, max_word_length);    /* add extended name */
  969.         if (is_set(STRaddsuffix)) {
  970.         if (numitems == 1) {
  971.             if (looking_for_lognames) {    /* add / */
  972.             catn(word, STRslash, max_word_length);
  973.             }
  974.             else if (looking_for_shellvar) {
  975.             struct varent *vp = adrof(extended_name);
  976.             Char   *stp;
  977.  
  978.             /*
  979.              * Don't consider array variables or empty variables
  980.              */
  981.             if (vp) {
  982.                 if (!(stp = vp->vec[0]) || vp->vec[0][0] == '\0' ||
  983.                 vp->vec[1]) {
  984.                 catn(word, STRspace, max_word_length);
  985.                 stp = NULL;
  986.                 }
  987.                 else
  988.                 stp = vp->vec[0];
  989.             }
  990.             else if ((stp = Getenv(extended_name)) == NULL)
  991.                 catn(word, STRspace, max_word_length);
  992.             if (stp != NULL) {
  993.                 *--target = '\0';
  994.                 (void) Strcat(tilded_dir, name);
  995.                 if (isadirectory(tilded_dir, stp))
  996.                 catn(word, STRslash, max_word_length);
  997.                 else
  998.                 catn(word, STRspace, max_word_length);
  999.             }
  1000.             }
  1001.             else if (looking_for_file || looking_for_command) {
  1002.             if (isadirectory(tilded_dir, extended_name)) {
  1003.                 catn(word, STRslash, max_word_length);
  1004.             }
  1005.             else {
  1006.                 catn(word, STRspace, max_word_length);
  1007.             }
  1008.             }
  1009.         }
  1010.         }
  1011.     }
  1012.     return (numitems);    /* at the end */
  1013.     }
  1014.     else if (command == LIST) {
  1015.     register int max_items = 0;
  1016.     register Char *cp;
  1017.  
  1018.     if (cp = value(STRlistmax)) {
  1019.         while (*cp) {
  1020.         if (!Isdigit(*cp)) {
  1021.             max_items = 0;
  1022.             break;
  1023.         }
  1024.         max_items = max_items * 10 + *cp++ - '0';
  1025.         }
  1026.     }
  1027.  
  1028.     if ((max_items > 0) && (numitems > max_items) && list_max) {
  1029.         char    tc;
  1030.  
  1031.         xprintf("There are %d items, list them anyway? [n/y] ", numitems);
  1032.         flush();
  1033.         /* We should be in Rawmode here, so no \n to catch */
  1034.         (void) read(SHIN, &tc, 1);
  1035.         xprintf("%c\r\n", tc);    /* echo the char, do a newline */
  1036.         if ((tc != 'y') && (tc != 'Y'))
  1037.         goto done_list;
  1038.     }
  1039.     qsort((ptr_t) items, (size_t) numitems, sizeof(items[1]), 
  1040.           (int (*) __P((const void *, const void *))) fcompare);
  1041.  
  1042.     print_by_column(STRNULL, items, numitems, TRUE);
  1043.  
  1044. done_list:
  1045.     if (items != NULL)
  1046.         FREE_ITEMS(items, numitems);
  1047.     return (numitems);
  1048.     }
  1049.     else if (command == SPELL) {
  1050.     if (looking_for_lognames)
  1051.         copyn(word, STRtilde, 1);
  1052.     else if (looking_for_shellvar) {
  1053.         Char   *ptr = Strrchr(word, '$');
  1054.  
  1055.         *++ptr = '\0';    /* Delete after the dollar */
  1056.     }
  1057.     else if (looking_for_file)
  1058.         copyn(word, dir, max_word_length);    /* put back dir part */
  1059.     else
  1060.         word[0] = '\0';
  1061.     catn(word, extended_name, max_word_length);    /* add extended name */
  1062.     return (d);
  1063.     }
  1064.     else {
  1065.     xprintf("Bad tw_command\n");
  1066.     return (0);
  1067.     }
  1068. }
  1069.  
  1070.  
  1071.  
  1072. /* stuff for general command line hacking */
  1073.  
  1074. /*
  1075.  * Strip next directory from path; return ptr to next unstripped directory.
  1076.  */
  1077.  
  1078. #ifdef notdef
  1079. Char * extract_dir_from_path(path, dir)
  1080.     Char   *path, dir[];
  1081. {
  1082.     register Char *d = dir;
  1083.  
  1084.     while (*path && (*path == ' ' || *path == ':'))
  1085.     path++;
  1086.     while (*path && (*path != ' ' && *path != ':'))
  1087.     *(d++) = *(path++);
  1088.     while (*path && (*path == ' ' || *path == ':'))
  1089.     path++;
  1090.  
  1091.     ++dirctr;
  1092.     if (*dir == '.')
  1093.     (void) Strcpy(dirflag, STRdotsp);
  1094.     else {
  1095.     dirflag[0] = ' ';
  1096.     if (dirctr <= 9) {
  1097.         dirflag[1] = '0' + dirctr;
  1098.         dirflag[2] = '\0';
  1099.     }
  1100.     else {
  1101.         dirflag[1] = '0' + dirctr / 10;
  1102.         dirflag[2] = '0' + dirctr % 10;
  1103.         dirflag[3] = '\0';
  1104.     }
  1105.     }
  1106.     *(d++) = '/';
  1107.     *d = 0;
  1108.  
  1109.     return path;
  1110. }
  1111.  
  1112. #endif
  1113.  
  1114.  
  1115. static void
  1116. free_items(items, numitems)
  1117.     register Char **items;
  1118.     register int numitems;
  1119. {
  1120.     register int i;
  1121.  
  1122. /*     for (i = 0; items[i] != (Char *)NULL; i++) */
  1123.     for (i = 0; i < numitems; i++)
  1124.     xfree((ptr_t) items[i]);
  1125.     xfree((ptr_t) items);
  1126.     maxitems = 0;
  1127. }
  1128.  
  1129.  
  1130. /*
  1131.  * parse full path in file into 2 parts: directory and file names
  1132.  * Should leave final slash (/) at end of dir.
  1133.  */
  1134. static void
  1135. extract_dir_and_name(path, dir, name)
  1136.     Char   *path, *dir, *name;
  1137. {
  1138.     register Char *p;
  1139.  
  1140.     p = Strrchr(path, '/');
  1141.     if (p == NULL) {
  1142.     copyn(name, path, MAXNAMLEN);
  1143.     dir[0] = '\0';
  1144.     }
  1145.     else {
  1146.     p++;
  1147.     copyn(name, p, MAXNAMLEN);
  1148.     copyn(dir, path, p - path);
  1149.     }
  1150. }
  1151.  
  1152. static Char *
  1153. getentry(dir_fd, looking_for_lognames)
  1154.     DIR    *dir_fd;
  1155.     int     looking_for_lognames;
  1156. {
  1157.     register struct passwd *pw;
  1158.     static Char retname[MAXPATHLEN];
  1159.  
  1160.     register struct dirent *dirp;
  1161.  
  1162.     if (looking_for_lognames) {    /* Is it login names we want? */
  1163.     /*
  1164.      * We don't want to get interrupted inside getpwent()
  1165.      * because the yellow pages code is not interruptible,
  1166.      * and if we call endpwent() immediatetely after
  1167.      * (in pintr()) we may be freeing an invalid pointer
  1168.      */
  1169. #ifdef BSDSIGS
  1170.     sigmask_t omask = sigblock(sigmask(SIGINT));
  1171. #else
  1172.     (void) sighold(SIGINT);
  1173. #endif /* BSDSIGS */
  1174.     /* ISC does not declare getpwent()? */
  1175.     pw = (struct passwd *) getpwent();
  1176. #ifdef BSDSIGS
  1177.     (void) sigsetmask(omask);
  1178. #else
  1179.     (void) sigrelse(SIGINT);
  1180. #endif /* BSDSIGS */
  1181.  
  1182.     if (pw == NULL) {
  1183. #ifdef YPBUGS
  1184.         fix_yp_bugs();
  1185. #endif
  1186.         return (NULL);
  1187.     }
  1188.     (void) Strcpy(retname, str2short(pw->pw_name));
  1189.     return (retname);
  1190.     }
  1191.     else {            /* It's a dir entry we want */
  1192.     if (dirp = readdir(dir_fd)) {
  1193.         (void) Strcpy(retname, str2short(dirp->d_name));
  1194.         return (retname);
  1195.     }
  1196.     return (NULL);
  1197.     }
  1198. }
  1199.  
  1200. /*
  1201.  * expand "/$old1/$old2/old3/"
  1202.  * to "/value_of_old1/value_of_old2/old3/"
  1203.  */
  1204. static Char *
  1205. dollar(new, old)
  1206.     Char   *new, *old;
  1207. {
  1208.     Char   *var, *val, *p, save;
  1209.     int     space;
  1210.  
  1211.     for (space = FILSIZ, p = new; *old && space > 0;)
  1212.     if (*old != '$') {
  1213.         *p++ = *old++;
  1214.         space--;
  1215.     }
  1216.     else {
  1217.         struct varent *vp;
  1218.  
  1219.         /* found a variable, expand it */
  1220.         for (var = ++old; alnum(*old); old++);
  1221.         save = *old;
  1222.         *old = '\0';
  1223.         vp = adrof(var);
  1224.         val = (!vp) ? Getenv(var) : NULL;
  1225.         *old = save;
  1226.         /*
  1227.          * Don't expand array variables
  1228.          */
  1229.         if (vp) {
  1230.         if (!vp->vec[0] || vp->vec[1]) {
  1231.             *new = '\0';
  1232.             return (NULL);
  1233.         }
  1234.         else
  1235.             val = vp->vec[0];
  1236.         }
  1237.         else if (!val) {
  1238.         *new = '\0';
  1239.         return (NULL);
  1240.         }
  1241.         for (; space > 0 && *val; space--)
  1242.         *p++ = *val++;
  1243.     }
  1244.     *p = '\0';
  1245.     return (new);
  1246. }
  1247.  
  1248. /*
  1249.  * expand "old" file name with possible tilde usage
  1250.  *        ~person/mumble
  1251.  * expands to
  1252.  *        home_directory_of_person/mumble
  1253.  * into string "new".
  1254.  */
  1255.  
  1256. static Char *
  1257. tilde(new, old)
  1258.     Char   *new, *old;
  1259. {
  1260.     register Char *o, *p;
  1261.  
  1262.     if ((old[0] != '~') &&
  1263.     (old[0] != '=' || (!Isdigit(old[1]) && old[1] != '-'))) {
  1264.     (void) Strcpy(new, old);
  1265.     return (new);
  1266.     }
  1267.  
  1268.     for (p = new, o = &old[1]; *o && *o != '/'; *p++ = *o++);
  1269.     *p = '\0';
  1270.  
  1271.     if (old[0] == '~') {
  1272.     if (gethdir(new)) {
  1273.         new[0] = '\0';
  1274.         return (NULL);
  1275.     }
  1276.     }
  1277.     else {            /* '=' stack expansion */
  1278.     if (!getstakd(new, (old[1] == '-') ? -1 : old[1] - '0')) {
  1279.         new[0] = '\0';
  1280.         return (NULL);
  1281.     }
  1282.     }
  1283.     (void) Strcat(new, o);
  1284.     return (new);
  1285. }
  1286.  
  1287. static  Char
  1288. filetype(dir, file)        /* symbology from 4.3 ls command */
  1289.     Char   *dir, *file;
  1290. {
  1291.     if (dir) {
  1292.     Char    path[512];
  1293.     char   *ptr;
  1294.     struct stat statb;
  1295.  
  1296.     (void) Strcpy(path, dir);
  1297.     catn(path, file, sizeof(path) / sizeof(Char));
  1298.  
  1299.     if (lstat(ptr = short2str(path), &statb) != -1)
  1300.         /* see above #define of lstat */
  1301.     {
  1302. #ifdef S_ISLNK
  1303.         if (S_ISLNK(statb.st_mode)) {    /* Symbolic link */
  1304.         if (adrof(STRlistlinks)) {
  1305.             if (stat(ptr, &statb) == -1)
  1306.             return ('&');
  1307.             else if (S_ISDIR(statb.st_mode))
  1308.             return ('>');
  1309.             else
  1310.             return ('@');
  1311.         }
  1312.         else
  1313.             return ('@');
  1314.         }
  1315. #endif
  1316. #ifdef S_ISSOCK
  1317.         if (S_ISSOCK(statb.st_mode))    /* Socket */
  1318.         return ('=');
  1319. #endif
  1320. #ifdef S_ISFIFO
  1321.         if (S_ISFIFO(statb.st_mode)) /* Named Pipe */
  1322.         return ('|');
  1323. #endif
  1324. #ifdef S_ISHIDDEN
  1325.         if (S_ISHIDDEN(statb.st_mode)) /* Hidden Directory [aix] */
  1326.         return ('+');
  1327. #endif
  1328. #ifdef S_ISCDF    
  1329.         if (S_ISCDF(statb.st_mode))    /* Context Dependent Files [hpux] */
  1330.         return ('+');
  1331. #endif 
  1332. #ifdef S_ISNWK
  1333.         if (S_ISNWK(statb.st_mode)) /* Network Special [hpux] */
  1334.         return (':');
  1335. #endif
  1336.         if (S_ISCHR(statb.st_mode))    /* char device */
  1337.         return ('%');
  1338.         if (S_ISBLK(statb.st_mode))    /* block device */
  1339.         return ('#');
  1340.         if (S_ISDIR(statb.st_mode))    /* normal Directory */
  1341.         return ('/');
  1342.         if (statb.st_mode & 0111)
  1343.         return ('*');
  1344.     }
  1345.     }
  1346.     return (' ');
  1347. }
  1348.  
  1349. static int
  1350. isadirectory(dir, file)        /* return 1 if dir/file is a directory */
  1351.     Char   *dir, *file;        /* uses stat rather than lstat to get dest. */
  1352. {
  1353.     if (dir) {
  1354.     Char    path[MAXPATHLEN];
  1355.     struct stat statb;
  1356.  
  1357.     (void) Strcpy(path, dir);
  1358.     catn(path, file, sizeof(path) / sizeof(Char));
  1359.     if (stat(short2str(path), &statb) >= 0) {    /* resolve through
  1360.                              * symlink */
  1361. #ifdef S_ISSOCK
  1362.         if (S_ISSOCK(statb.st_mode))    /* Socket */
  1363.         return 0;
  1364. #endif
  1365. #ifdef S_ISFIFO
  1366.         if (S_ISFIFO(statb.st_mode))    /* Named Pipe */
  1367.         return 0;
  1368. #endif
  1369.         if (S_ISDIR(statb.st_mode))    /* normal Directory */
  1370.         return 1;
  1371.     }
  1372.     }
  1373.     return 0;
  1374. }
  1375.  
  1376. /*
  1377.  * Print sorted down columns
  1378.  */
  1379. void
  1380. print_by_column(dir, items, count, no_file_suffix)
  1381.     register Char *dir, *items[];
  1382.     int     count, no_file_suffix;
  1383. {
  1384.     register int i, r, c, columns, rows;
  1385.     unsigned int w, maxwidth = 0;
  1386.     extern int Tty_raw_mode;
  1387.  
  1388.     lbuffed = 0;        /* turn off line buffering */
  1389.  
  1390.     for (i = 0; i < count; i++)    /* find widest string */
  1391.     maxwidth = max(maxwidth, Strlen(items[i]));
  1392.  
  1393.     maxwidth += no_file_suffix ? 1 : 2;    /* for the file tag and space */
  1394.     columns = (TermH + 1) / maxwidth;    /* PWP: terminal size change */
  1395.     if (!columns)
  1396.     columns = 1;
  1397.     rows = (count + (columns - 1)) / columns;
  1398.  
  1399.     for (r = 0; r < rows; r++) {
  1400.     for (c = 0; c < columns; c++) {
  1401.         i = c * rows + r;
  1402.  
  1403.         if (i < count) {
  1404.         w = Strlen(items[i]);
  1405.  
  1406.         if (no_file_suffix) {
  1407.             /* Print the command name */
  1408.             xprintf("%s", short2str(items[i]));
  1409.         }
  1410.         else {
  1411.             /* Print filename followed by '/' or '*' or ' ' */
  1412.             xprintf("%s%c", short2str(items[i]),
  1413.                 filetype(dir, items[i]));
  1414.             w++;
  1415.         }
  1416.  
  1417.         if (c < (columns - 1))    /* Not last column? */
  1418.             for (; w < maxwidth; w++)
  1419.             xputchar(' ');
  1420.         }
  1421.     }
  1422.     if (Tty_raw_mode)
  1423.         xputchar('\r');
  1424.     xputchar('\n');
  1425.     }
  1426.  
  1427.     lbuffed = 1;        /* turn back on line buffering */
  1428.     flush();
  1429. }
  1430.  
  1431.  
  1432. int
  1433. StrQcmp(str1, str2)
  1434.     register Char *str1, *str2;
  1435. {
  1436.     for (; *str1 && (*str1 & TRIM) == (*str2 & TRIM); str1++, str2++);
  1437.     /*
  1438.      * The following case analysis is necessary so that characters which look
  1439.      * negative collate low against normal characters but high against the
  1440.      * end-of-string NUL.
  1441.      */
  1442.     if (*str1 == '\0' && *str2 == '\0')
  1443.     return (0);
  1444.     else if (*str1 == '\0')
  1445.     return (-1);
  1446.     else if (*str2 == '\0')
  1447.     return (1);
  1448.     else
  1449.     return ((*str1 & TRIM) - (*str2 & TRIM));
  1450. }
  1451.  
  1452. /*
  1453.  * For qsort()
  1454.  */
  1455. int
  1456. fcompare(file1, file2)
  1457.     Char  **file1, **file2;
  1458. {
  1459. #if defined(NLS) && !defined(NOSTRCOLL)
  1460.     char    buf[2048];
  1461.  
  1462.     (void) strcpy(buf, short2str(*file1));
  1463.     return ((int) strcoll(buf, short2str(*file2)));
  1464. #else
  1465.     return (StrQcmp(*file1, *file2));
  1466. #endif
  1467. }
  1468.  
  1469. /*
  1470.  * Concatenate src onto tail of des.
  1471.  * Des is a string whose maximum length is count.
  1472.  * Always null terminate.
  1473.  */
  1474.  
  1475. void
  1476. catn(des, src, count)
  1477.     register Char *des, *src;
  1478.     register count;
  1479. {
  1480.     while (--count >= 0 && *des)
  1481.     des++;
  1482.     while (--count >= 0)
  1483.     if ((*des++ = *src++) == 0)
  1484.         return;
  1485.     *des = '\0';
  1486. }
  1487.  
  1488. /*
  1489.  * like strncpy but always leave room for trailing \0
  1490.  * and always null terminate.
  1491.  */
  1492. void
  1493. copyn(des, src, count)
  1494.     register Char *des, *src;
  1495.     register count;
  1496. {
  1497.     while (--count >= 0)
  1498.     if ((*des++ = *src++) == 0)
  1499.         return;
  1500.     *des = '\0';
  1501. }
  1502.  
  1503. static void
  1504. tw_get_comm_list()
  1505. {                /* stolen from sh.exec.c dohash() */
  1506.     register DIR *dirp;
  1507.     register struct dirent *dp;
  1508.     register Char *dir;
  1509.     register Char **pv;
  1510.     struct varent *v = adrof(STRpath);
  1511.  
  1512.     relatives_in_path = 0;    /* set to false until we know better */
  1513.     tw_clear_comm_list();
  1514.     if (v == 0)            /* if no path */
  1515.     return;
  1516.  
  1517.     if (adrof(STRrecognize_only_executables)) {
  1518.     for (pv = v->vec; *pv; pv++) {
  1519.         if (pv[0][0] != '/') {
  1520.         relatives_in_path = 1;
  1521.         continue;
  1522.         }
  1523.         dirp = opendir(short2str(*pv));
  1524.         if (dirp == NULL)
  1525.         continue;
  1526.  
  1527.         dir = Strspl(*pv, STRslash);
  1528.         while ((dp = readdir(dirp)) != NULL) {
  1529.         /* the call to executable() may make this a bit slow */
  1530.         if (dp->d_ino != 0 &&
  1531.             executable(dir, str2short(dp->d_name), 0))
  1532.             tw_add_comm_name(str2short(dp->d_name));
  1533.         }
  1534.         (void) closedir(dirp);
  1535.         xfree((ptr_t) dir);
  1536.     }
  1537.     }
  1538.     else {
  1539.     for (pv = v->vec; *pv; pv++) {
  1540.         if (pv[0][0] != '/') {
  1541.         relatives_in_path = 1;
  1542.         continue;
  1543.         }
  1544.         dirp = opendir(short2str(*pv));
  1545.         if (dirp == NULL)
  1546.         continue;
  1547.  
  1548.         while ((dp = readdir(dirp)) != NULL) {
  1549.         if (dp->d_ino != 0)
  1550.             tw_add_comm_name(str2short(dp->d_name));
  1551.         }
  1552.         (void) closedir(dirp);
  1553.     }
  1554.     }
  1555. }
  1556.